//package edu.buaa.common.server;
//
//import com.alibaba.fastjson.JSON;
//import com.google.common.base.Preconditions;
//import edu.buaa.common.RuntimeEnv;
//import edu.buaa.common.transaction.AbstractTransaction;
//import edu.buaa.common.transaction.*;
//import edu.buaa.common.utils.PFieldList;
//import edu.buaa.common.utils.PVal;
//import edu.buaa.utils.Helper;
//import edu.buaa.utils.Pair;
//import edu.buaa.utils.Triple;
//import org.neo4j.graphdb.*;
//import org.neo4j.graphdb.schema.IndexDefinition;
//import org.neo4j.graphdb.schema.Schema;
//
//import java.io.File;
//import java.io.IOException;
//import java.util.*;
//
//public class Neo4jServer1 extends DBSocketServer.ReqExecutor {
//
//    private final Map<String, Long> idMap2Long = new HashMap<>();
//
//    public static void main(String[] args) {
//        DBSocketServer server = new DBSocketServer(dbDir(), new Neo4jServer1(), 9833, "true".equals(System.getenv("ALLOW_TX_FAILURE")));
//        RuntimeEnv env = RuntimeEnv.getCurrentEnv();
//        String serverCodeVersion = env.name() + "." + Helper.codeGitVersion();
//        System.out.println("server code version:" + serverCodeVersion);
//        try {
//            server.start();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//    }
//
//    private static File dbDir() {
//        String path = Helper.mustEnv("DB_PATH");
//        Preconditions.checkNotNull(path, "need arg: DB_PATH");
//        File dbDir = new File(path);
//        if (!dbDir.exists()) {
//            if (dbDir.mkdirs()) return dbDir;
//            else throw new IllegalArgumentException("invalid dbDir");
//        } else if (!dbDir.isDirectory()) {
//            throw new IllegalArgumentException("invalid dbDir");
//        }
//        return dbDir;
//    }
//
//    private enum Edge implements RelationshipType {
//        NORMAL, PROP, TIME_LINE, TIME
//    }
//
//    private Label label(String name){
//        return DynamicLabel.label(name);
//    }
//
//    @Override
//    public void setDB(GraphDatabaseService db) {
//        this.db = db;
//        try (Transaction tx = db.beginTx()) {
//            Schema schema = db.schema();
//            boolean hasTimeIndex = false;
//            try {
//                for(IndexDefinition timeIndex : schema.getIndexes()){
//                    hasTimeIndex = true;
//                }
//                System.out.println("Has the time index already.");
//            } catch (IllegalArgumentException e) {
//                // There is no this index
//            }
//            if (!hasTimeIndex) {
//                schema.indexFor(label("NODE")).on("id").create();
//                System.out.println("Build the NODE index.");
//                schema.indexFor(label("Time")).on("st_time").create(); //.on("en_time")
//                System.out.println("Build the TIME index.");
//            }
//            tx.success();
//        }
//        buildIdMap(db);
//    }
//
//
//    @Override
//    protected AbstractTransaction.Result execute(String line) throws RuntimeException {
//        AbstractTransaction tx = JSON.parseObject(line, AbstractTransaction.class);
//        switch (tx.getTxType()) {
//            case tx_import_static_data:
//                return execute((ImportStaticDataTx) tx);
//            case tx_import_temporal_data:
//                return execute((ImportTemporalDataTx) tx);
//            case tx_update_temporal_data:
//                return execute((UpdateTemporalDataTx) tx);
//            case tx_query_snapshot:
//                return execute((SnapshotQueryTx) tx);
//            case tx_query_snapshot_aggr_max:
//                return execute((SnapshotAggrMaxTx) tx);
//            case tx_query_snapshot_aggr_duration:
//                return execute((SnapshotAggrDurationTx) tx);
//            case tx_query_entity_history:
//                return execute((EntityHistoryTx) tx);
//            case tx_query_road_by_temporal_condition:
//                return execute((EntityTemporalConditionTx) tx);
//            default:
//                throw new UnsupportedOperationException();
//        }
//    }
//
//    private void buildIdMap(GraphDatabaseService db) {
//        try (Transaction tx = db.beginTx();
//             ResourceIterator<Node> allDataNodes = db.findNodes(label("NODE"))) {
//            while (allDataNodes.hasNext()) {
//                Node n = allDataNodes.next();
//                String idInt = (String) n.getProperty("id");
//                idMap2Long.put(idInt, n.getId());
//            }
//            tx.success();
//        }
//    }
//
//
//    private AbstractTransaction.Result execute(ImportStaticDataTx tx) {
//        Label dataNode = label("NODE");
//        try (Transaction t = db.beginTx()) {
//            PFieldList nodesData = tx.getNodes();
//            int nSize = nodesData.size();
//            for (int i=0; i<nSize; i++) {
//                Node n = db.createNode(dataNode);
//                String id = nodesData.get("id", i).s();
//                n.setProperty("id", id);
//                for(String key : nodesData.keys()){
//                    n.setProperty(key, nodesData.get(key, i).getVal());
//                }
//                idMap2Long.put(id, n.getId());
//            }
//            PFieldList relsData = tx.getRels();
//            int rSize = relsData.size();
//            for (int i=0; i<rSize; i++) {
//                String fromId = relsData.get("from", i).s();
//                String toId = relsData.get("to", i).s();
//                Node s = db.getNodeById(idMap2Long.get(fromId));
//                Node e = db.getNodeById(idMap2Long.get(toId));
//                Relationship r = s.createRelationshipTo(e, Edge.NORMAL);
//                for(String key : relsData.keys()){
//                    r.setProperty(key, relsData.get(key, i).getVal());
//                }
//            }
//            t.success();
//        }
//        return new AbstractTransaction.Result();
//    }
//
////    private void setProperties(List<Float> values, Relationship r){
////        for(int i = 0; i< EnergyDataTxGenerator.properties.length; i++) {
////            String propName = EnergyDataTxGenerator.properties[i];
////            Float val = values.get(i);
////            r.setProperty(propName, val);
////        }
////    }
//
//    // Assume that data comes order by time. Thus, multithreading would not work.
//    private AbstractTransaction.Result execute(ImportTemporalDataTx tx) {
//        try (Transaction ttx = db.beginTx()) {
//            PFieldList data = tx.getData();
//            int pSize = data.size();
//            for (int i=0; i<pSize; i++) {
//                Long id = idMap2Long.get(data.get("NodeId());
//                assert id != null;
//                Node n = db.getNodeById(id);
//                Node t = getProperTimeNode(s.getTime(), s.getTime()+3599);
//                Relationship r = n.createRelationshipTo(t, Edge.TIME);
//                setProperties(s.getStatus(), r);
//            }
//            ttx.success();
//        }
//        return new AbstractTransaction.Result();
//    }
//
//    private Node getProperTimeNode(int startTime, int endTime) {
//        Node ret;
//        ResourceIterator<Node> nodes = db.findNodes(label("Time"), "st_time", startTime);
//        while (nodes.hasNext()) {
//            ret = nodes.next();
//            if((int)ret.getProperty("en_time")==endTime) return ret;
//        }
//        ret = db.createNode(label("Time"));
//        ret.setProperty("st_time", startTime);
//        ret.setProperty("en_time", endTime);
//        return ret;
//    }
//
//    private void copyProperty(Relationship from, Relationship to){
//        for(String propName : EnergyDataTxGenerator.properties) {
//            to.setProperty(propName, from.getProperty(propName));
//        }
//    }
//
//    private AbstractTransaction.Result execute(UpdateTemporalDataTx tx) {
//        try (Transaction t = db.beginTx()) {
//            Node node = db.findNode(label("NODE"), "id", tx.getEntityId());
//            // delete the old node and relationships.
//            ArrayList<Relationship> willBeDeleted = new ArrayList<>();
//            int mn = Integer.MAX_VALUE, mx = Integer.MIN_VALUE;
//            Relationship mnRel = null, mxRel = null;
//            for (Relationship r : node.getRelationships(Edge.TIME)) {
//                Node timeNode = r.getOtherNode(node);
//                int st = (int) timeNode.getProperty("st_time");
//                int en = (int) timeNode.getProperty("en_time");
//                if (tx.getEndTime() >= st && tx.getStartTime() <= en) {
//                    willBeDeleted.add(r);
//                    if (st < tx.getStartTime() && tx.getEndTime() < en) {
//                        mn = st;
//                        mx = en;
//                        mnRel = mxRel = r;
//                        break;
//                    }
//                    if (st < tx.getStartTime()) {
//                        mnRel = r;
//                        mn = st;
//                    }
//                    if (en > tx.getEndTime()) {
//                        mxRel = r;
//                        mx = en;
//                    }
//                }
//            }
//
//            // update the first and last range.
//            if(mnRel!=null){
//                Node t1 = getProperTimeNode(mn, tx.getStartTime() - 1);
//                Relationship r = node.createRelationshipTo(t1, Edge.TIME);
//                copyProperty(mnRel, r);
//            }
//
//            if(mxRel!=null){
//                Node t2 = getProperTimeNode(tx.getEndTime() + 1, mx);
//                Relationship r = node.createRelationshipTo(t2, Edge.TIME);
//                copyProperty(mxRel, r);
//            }
//
//            // delete the old value.
//            for (Relationship d : willBeDeleted) {
//                Node time = d.getOtherNode(node);
//                d.delete();
//                if (time.getDegree() == 0) time.delete();
//            }
//
//            // build the new node.
//            Node timeNode = getProperTimeNode(tx.getStartTime(), tx.getEndTime());
//            Relationship rel = node.createRelationshipTo(timeNode, Edge.TIME);
//            setProperties(tx.getValues(), rel);
//            t.success();
//        }
//        return new AbstractTransaction.Result();
//    }
//
//    private AbstractTransaction.Result execute(SnapshotQueryTx tx) {
//        try (Transaction t = db.beginTx()) {
//            List<Pair<Integer, Float>> res = new ArrayList<>();
//            int time = tx.getTimestamp();
//            ResourceIterator<Node> timeNodes = db.findNodes(label("Time"));
//            ArrayList<Node> cache = new ArrayList<>();
//            while (timeNodes.hasNext()) {
//                Node node = timeNodes.next();
//                int st = (int) node.getProperty("st_time");
//                int en = (int) node.getProperty("en_time");
//                if (time >= st && time <= en) cache.add(node);
//            }
//            for (Node tNode : cache) {
//                Iterable<Relationship> rels = tNode.getRelationships(Edge.TIME);
//                for (Relationship rel : rels) {
//                    res.add(Pair.of((Integer) rel.getOtherNode(tNode).getProperty("id"), (float) rel.getProperty(tx.getPropertyName())));
//                }
//            }
//            t.success();
//            SnapshotQueryTx.Result result = new SnapshotQueryTx.Result();
//            result.setStatus(res);
//            return result;
//        }
//    }
//
//    private AbstractTransaction.Result execute(SnapshotAggrMaxTx tx) {
//        try (Transaction t = db.beginTx()) {
//            List<Pair<Integer, Float>> res = new ArrayList<>();
//            int t0 = tx.getT0(), t1 = tx.getT1();
//            ResourceIterator<Node> timeNodes = db.findNodes(label("Time"));
//            ArrayList<Node> cache = new ArrayList<>();
//            HashMap<Integer, Float> ans = new HashMap<>();
//            while (timeNodes.hasNext()) {
//                Node node = timeNodes.next();
//                int st = (int) node.getProperty("st_time");
//                int en = (int) node.getProperty("en_time");
//                if (t1 >= st && t0 <= en) {
//                    cache.add(node);
//                }
//            }
//            for (Node node : cache) {
//                Iterable<Relationship> rels = node.getRelationships(Edge.TIME);
//                for (Relationship rel : rels) {
//                    ans.merge((Integer) rel.getOtherNode(node).getProperty("id"), (float) rel.getProperty(tx.getP()), Float::max);
//                }
//            }
//            for (Map.Entry<Integer, Float> entry : ans.entrySet()) {
//                res.add(Pair.of(entry.getKey(), entry.getValue()));
//            }
//            t.success();
//            SnapshotAggrMaxTx.Result result = new SnapshotAggrMaxTx.Result();
//            result.setPropMaxValue(res);
//            return result;
//        }
//    }
//
//    private AbstractTransaction.Result execute(SnapshotAggrDurationTx tx) {
//        TreeSet<Float> intStarts = tx.getIntStartTreeSet();
//        try (Transaction t = db.beginTx()) {
//            List<Triple<Integer, Float, Integer>> res = new ArrayList<>();
//            int t0 = tx.getT0(), t1 = tx.getT1();
//            ResourceIterator<Node> timeNodes = db.findNodes(label("Time"));
//            HashMap<Integer, HashMap<Float, Integer>> ans = new HashMap<>();
//            while (timeNodes.hasNext()) {
//                Node node = timeNodes.next();
//                int st = (int) node.getProperty("st_time");
//                int en = (int) node.getProperty("en_time");
//                if (t1 >= st && t0 <= en) {
//                    int left = Math.max(t0, st);
//                    int right = Math.min(t1, en);
//                    int duration = right - left + 1;
//
//                    for (Relationship rel : node.getRelationships(Edge.TIME)) {
//                        Integer id = (Integer) rel.getOtherNode(node).getProperty("id");
//                        float property = (Float) rel.getProperty(tx.getP());
//                        Float grp = intStarts.floor(property);
//                        if(grp!=null) {
//                            HashMap<Float, Integer> tmp = ans.get(id);
//                            if (tmp != null) {
//                                tmp.merge(grp, duration, Integer::sum);
//                            } else {
//                                tmp = new HashMap<>();
//                                tmp.put(grp, duration);
//                            }
//                            ans.put(id, tmp);
//                        }
//                    }
//                }
//            }
//            for (Map.Entry<Integer, HashMap<Float, Integer>> entry : ans.entrySet()) {
//                for (Map.Entry<Float, Integer> e : entry.getValue().entrySet()) {
//                    res.add(Triple.of(entry.getKey(), e.getKey(), e.getValue()));
//                }
//            }
//            t.success();
//            SnapshotAggrDurationTx.Result result = new SnapshotAggrDurationTx.Result();
//            result.setStatusDuration(res);
//            return result;
//        }
//    }
//
//    private AbstractTransaction.Result execute(EntityHistoryTx tx) {
//        try (Transaction t = db.beginTx()) {
//            List<Triple<Integer, Integer, Float>> res = new ArrayList<>();
//            int t0 = tx.getBeginTime(), t1 = tx.getEndTime();
//            ResourceIterator<Node> timeNodes = db.findNodes(label("Time"));
//            while (timeNodes.hasNext()) {
//                Node node = timeNodes.next();
//                int st = (int) node.getProperty("st_time");
//                int en = (int) node.getProperty("en_time");
//                if (t1 >= st && t0 <= en) {
//                    int left = Math.max(t0, st);
//                    int right = Math.min(t1, en);
//
//                    for (Relationship rel : node.getRelationships(Edge.TIME)) {
//                        Integer id = (Integer) rel.getOtherNode(node).getProperty("id");
//                        if(id==tx.getEntity()){
//                            float property = (Float) rel.getProperty(tx.getProp());
//                            res.add(Triple.of(left, right, property));
//                        }
//                    }
//                }
//            }
//            t.success();
//            EntityHistoryTx.Result result = new EntityHistoryTx.Result();
//            result.setHistory(res);
//            return result;
//        }
//    }
//
//    private AbstractTransaction.Result execute(EntityTemporalConditionTx tx) {
//        try (Transaction t = db.beginTx()) {
//            int t0 = tx.getT0(), t1 = tx.getT1();
//            float vMin = tx.getVMin(), vMax = tx.getVMax();
//            ResourceIterator<Node> timeNodes = db.findNodes(label("Time"));
//            HashSet<Integer> res = new HashSet<>();
//            while (timeNodes.hasNext()) {
//                Node node = timeNodes.next();
//                int st = (int) node.getProperty("st_time");
//                int en = (int) node.getProperty("en_time");
//                if (t1 >= st && t0 <= en) {
//                    for (Relationship rel : node.getRelationships(Edge.TIME)) {
//                        Float property = (Float) rel.getProperty(tx.getP());
//                        int node_id = (Integer) rel.getOtherNode(node).getProperty("id");
//                        if (property >= vMin && property <= vMax) {
//                            res.add(node_id);
//                        }
//                    }
//                }
//            }
//            t.success();
//            EntityTemporalConditionTx.Result result = new EntityTemporalConditionTx.Result();
//            result.setEntities(new ArrayList<>(res));
//            return result;
//        }
//    }
//
//}
